package com.codi.base.web;

import static com.codi.base.web.Constants.POST;
import static com.codi.base.web.Constants.UTF8;

import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URLDecoder;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.util.UrlPathHelper;

import com.alibaba.fastjson.JSON;
import com.codi.base.log.desensitizer.DesensitizeModel;
import com.codi.base.util.ExceptionUtil;
import com.codi.base.util.StringUtil;

/**
 * HttpServletRequest帮助类
 * 
 * @author Shangdu Lin
 * 
 */
public class RequestUtils {
	private static final Logger log = LoggerFactory.getLogger(RequestUtils.class);

	/**
	 * 获取QueryString的参数，并使用URLDecoder以UTF-8格式转码。如果请求是以post方法提交的，
	 * 那么将通过HttpServletRequest#getParameter获取。
	 * 
	 * @param request
	 *            web请求
	 * @param name
	 *            参数名称
	 * @return
	 */
	public static String getQueryParam(HttpServletRequest request, String name) {
		if (StringUtils.isBlank(name)) {
			return null;
		}
		if (request.getMethod().equalsIgnoreCase(POST)) {
			return request.getParameter(name);
		}
		String s = request.getQueryString();
		if (StringUtils.isBlank(s)) {
			return null;
		}
		try {
			s = URLDecoder.decode(s, UTF8);
		} catch (UnsupportedEncodingException e) {
			log.error("encoding " + UTF8 + " not support?", e);
		}
		String[] values = parseQueryString(s).get(name);
		if (values != null && values.length > 0) {
			return values[values.length - 1];
		} else {
			return null;
		}
	}

	public static String[] getQueryParamValues(HttpServletRequest request, String name) {
		if (StringUtils.isBlank(name)) {
			return null;
		}
		if (request.getMethod().equalsIgnoreCase(POST)) {
			return request.getParameterValues(name);
		} else {
			return null;
		}
	}

	@SuppressWarnings("unchecked")
	public static Map<String, Object> getQueryParams(HttpServletRequest request) {
		Map<String, String[]> map;
		if (request.getMethod().equalsIgnoreCase(POST)) {
			map = request.getParameterMap();
		} else {
			String s = request.getQueryString();
			if (StringUtils.isBlank(s)) {
				return new HashMap<String, Object>();
			}
			try {
				s = URLDecoder.decode(s, UTF8);
			} catch (UnsupportedEncodingException e) {
				log.error("encoding " + UTF8 + " not support?", e);
			}
			map = parseQueryString(s);
		}

		Map<String, Object> params = new HashMap<String, Object>(map.size());
		int len;
		for (Map.Entry<String, String[]> entry : map.entrySet()) {
			len = entry.getValue().length;
			if (len == 1) {
				params.put(entry.getKey(), entry.getValue()[0]);
			} else if (len > 1) {
				params.put(entry.getKey(), entry.getValue());
			}
		}
		return params;
	}

	/**
	 * 
	 * Parses a query string passed from the client to the server and builds a
	 * <code>HashTable</code> object with key-value pairs. The query string
	 * should be in the form of a string packaged by the GET or POST method,
	 * that is, it should have key-value pairs in the form <i>key=value</i>,
	 * with each pair separated from the next by a &amp; character.
	 * 
	 * <p>
	 * A key can appear more than once in the query string with different
	 * values. However, the key appears only once in the hashtable, with its
	 * value being an array of strings containing the multiple values sent by
	 * the query string.
	 * 
	 * <p>
	 * The keys and values in the hashtable are stored in their decoded form, so
	 * any + characters are converted to spaces, and characters sent in
	 * hexadecimal notation (like <i>%xx</i>) are converted to ASCII characters.
	 * 
	 * @param s
	 *            a string containing the query to be parsed
	 * 
	 * @return a <code>HashTable</code> object built from the parsed key-value
	 *         pairs
	 * 
	 * @exception IllegalArgumentException
	 *                if the query string is invalid
	 * 
	 */
	public static Map<String, String[]> parseQueryString(String s) {
		String valArray[] = null;
		if (s == null) {
			throw new IllegalArgumentException();
		}
		Map<String, String[]> ht = new HashMap<String, String[]>();
		StringTokenizer st = new StringTokenizer(s, "&");
		while (st.hasMoreTokens()) {
			String pair = (String) st.nextToken();
			int pos = pair.indexOf('=');
			if (pos == -1) {
				continue;
			}
			String key = pair.substring(0, pos);
			String val = pair.substring(pos + 1, pair.length());
			if (ht.containsKey(key)) {
				String oldVals[] = ht.get(key);
				valArray = new String[oldVals.length + 1];
				for (int i = 0; i < oldVals.length; i++) {
					valArray[i] = oldVals[i];
				}
				valArray[oldVals.length] = val;
			} else {
				valArray = new String[1];
				valArray[0] = val;
			}
			ht.put(key, valArray);
		}
		return ht;
	}

	public static Map<String, String> getRequestMap(HttpServletRequest request, String prefix) {
		return getRequestMap(request, prefix, false);
	}

	public static Map<String, String> getRequestMapWithPrefix(HttpServletRequest request, String prefix) {
		return getRequestMap(request, prefix, true);
	}

	@SuppressWarnings("unchecked")
	private static Map<String, String> getRequestMap(HttpServletRequest request, String prefix,
			boolean nameWithPrefix) {
		Map<String, String> map = new HashMap<String, String>();
		Enumeration<String> names = request.getParameterNames();
		String name, key, value;
		while (names.hasMoreElements()) {
			name = names.nextElement();
			if (name.startsWith(prefix)) {
				key = nameWithPrefix ? name : name.substring(prefix.length());
				value = StringUtils.join(request.getParameterValues(name), ',');
				map.put(key, value);
			}
		}
		return map;
	}

	@SuppressWarnings("unchecked")
	public static Map<String, String> getRequestMap(HttpServletRequest request) {
		Map<String, String> map = new HashMap<String, String>();
		Enumeration<String> names = request.getParameterNames();
		String name, key, value;
		while (names.hasMoreElements()) {
			name = names.nextElement();
			key = name;
			value = StringUtils.join(request.getParameterValues(name), ',');
			map.put(key, value);

		}
		return map;
	}

	/*
	 * REQUEST.GETREMOTEADDR() 192.168.239.196
	 * 
	 * REQUEST.GETHEADER("X-FORWARDED-FOR") 58.63.227.162, 192.168.237.178,
	 * 192.168.238.218
	 * 
	 * REQUEST.GETHEADER("X-REAL-IP") 192.168.238.218
	 * 
	 * 所以访问的流程应该是这样，客户端58.63.227.162发出请求，经过192.168.237.178,
	 * 192.168.238.218两层转发，到了192.168.239.196这台NGINX上，NGINX就把X-REAL-
	 * IP头设成了自己看到的REMOTE_ADDR，也就是直接发给到他的192.168.238.218，这时候RESIN收到这个包，
	 * 对RESIN来说直接发给他的REMOTE_ADDR就是NGINX的IP，也就是192.168.239.196，那么RESIN里面的REQUEST.
	 * GETREMOTEADDR()就是192.168.239.196，那么在RESIN里拿最原始的IP逻辑（也就是拿能够知道的最外层的IP）
	 * 应该是这样：
	 * 
	 * 如果XFF不为空，拿XFF的左边第一个
	 * 
	 * 如果XFF为空，拿XRI
	 * 
	 * 如果XRI为空，只能拿REQUEST.GETREMOTEADDR()，也就是只能拿到最直接发给他的机器IP了，
	 */
	public static String getIpAddr(HttpServletRequest request) {
		String ipAddress = request.getHeader("x-forwarded-for");
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getHeader("Proxy-Client-IP");
		}
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getHeader("HTTP_CLIENT_IP");
		}
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
		}
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getRemoteAddr();

			// 这里主要是获取本机的ip,可有可无
			if (ipAddress.equals("127.0.0.1") || ipAddress.endsWith("0:0:0:0:0:0:1")) {
				// 根据网卡取本机配置的IP
				InetAddress inet = null;
				try {
					inet = InetAddress.getLocalHost();
					ipAddress = inet.getHostAddress();
				} catch (UnknownHostException e) {
					e.printStackTrace();
				}				
			}

		}

		// 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
		if (ipAddress != null && ipAddress.length() != 0) { // "***.***.***.***".length()
															// = 15
			if (ipAddress.indexOf(",") > 0) {
				ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
			}
		}

		return ipAddress;

	}

	/**
	 * 获得当的访问路径
	 * 
	 * HttpServletRequest.getRequestURL+"?"+HttpServletRequest.getQueryString
	 * 
	 * @param request
	 * @return
	 */
	public static String getLocation(HttpServletRequest request) {
		UrlPathHelper helper = new UrlPathHelper();
		StringBuffer buff = request.getRequestURL();
		String uri = request.getRequestURI();
		String origUri = helper.getOriginatingRequestUri(request);
		buff.replace(buff.length() - uri.length(), buff.length(), origUri);
		String queryString = helper.getOriginatingQueryString(request);
		if (queryString != null) {
			buff.append("?").append(queryString);
		}
		return buff.toString();
	}

	/**
	 * 获得请求的session id，但是HttpServletRequest#getRequestedSessionId()方法有一些问题。
	 * 当存在部署路径的时候，会获取到根路径下的jsessionid。
	 * 
	 * @see HttpServletRequest#getRequestedSessionId()
	 * 
	 * @param request
	 * @return
	 */
	public static String getRequestedSessionId(HttpServletRequest request) {
		String sid = request.getRequestedSessionId();
		String ctx = request.getContextPath();
		// 如果session id是从url中获取，或者部署路径为空，那么是在正确的。
		if (request.isRequestedSessionIdFromURL() || StringUtils.isBlank(ctx)) {
			return sid;
		} else {
			// 手动从cookie获取
			Cookie cookie = CookieUtils.getCookie(request, Constants.JSESSION_COOKIE);
			if (cookie != null) {
				return cookie.getValue();
			} else {
				return null;
			}
		}

	}

	/**
	 * 获取ServletRequest中的参数键值串 例如：name=zhangsan&password=sanzhang&vcode=1234
	 * 
	 * @param request
	 * @return
	 */
	public static String getQueryString(HttpServletRequest request) {
		@SuppressWarnings("unchecked")
		Map<String, String[]> params = request.getParameterMap();

		Map<String, String> map = new HashMap<>();
		for (String key : params.keySet()) {
			String[] values = params.get(key);
			String value = "";
			for (int i = 0; i < values.length; i++) {
				if (!StringUtil.isEmpty(value)) {
					value = value + ",";
				}
				value = values[i];
			}

			map.put(key, value);
		}

		return JSON.toJSONString(map);
	}

	/**
	 * 获取请求头信息
	 * 
	 * @param request
	 * @return
	 */
	public static Map<String, String> getQueryHeaders(HttpServletRequest request) {

		Map<String, String> map = new HashMap<String, String>();
		@SuppressWarnings("rawtypes")
		Enumeration headerNames = request.getHeaderNames();
		while (headerNames.hasMoreElements()) {
			String key = (String) headerNames.nextElement();
			String value = request.getHeader(key);
			map.put(key, value);
		}

		return map;
	}

	/**
	 * 获取ServletRequest中的参数键值串 例如：name=zhangsan&password=sanzhang&vcode=1234，
	 * 并且脱敏敏感字段
	 * 
	 * @param request
	 * @param desRules
	 * @return
	 */
	public static String getQueryString(HttpServletRequest request, Map<String, DesensitizeModel> desRules) {
		@SuppressWarnings("unchecked")
		Map<String, String[]> params = request.getParameterMap();
		String queryString = "";
		for (String key : params.keySet()) {
			String[] values = params.get(key);
			for (int i = 0; i < values.length; i++) {
				String value = values[i];
				try {
					// 如果该字段是敏感字段，则需要脱敏
					if (desRules != null && desRules.containsKey(key)) {
						// 获取脱敏规则
						DesensitizeModel model = desRules.get(key);
						// 规则不能为空
						if (model != null && StringUtil.isNotEmpty(model.getExpression())
								&& StringUtil.isNotEmpty(model.getReplacement())) {
							Pattern pattern = Pattern.compile(model.getExpression());
							Matcher matcher = pattern.matcher(value);
							value = matcher.replaceAll(model.getReplacement());
						}
					}
				} catch (Exception exception) {
					ExceptionUtil.logError(log, "getQueryString", exception);
				}
				queryString += key + "=" + value + "&";
			}
		}
		// 去掉最后一个&
		queryString = queryString.substring(0, queryString.length() - 1);
		return queryString;
	}

	/**
	 * 获取Header
	 * 
	 * @param request
	 * @param headerKey
	 * @return
	 */
	public static String getHeader(HttpServletRequest request, String headerKey) {
		return request.getHeader(headerKey);
	}

	/**
	 * 获取Header
	 * 
	 * @param request
	 * @param headerKey
	 * @param defaultValue
	 *            默认值
	 * @return
	 */
	public static String getHeader(HttpServletRequest request, String headerKey, String defaultValue) {
		String value = getHeader(request, headerKey);
		if (StringUtil.isEmpty(value)) {
			return defaultValue;
		}
		return value;
	}

}
